﻿using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using TiffLibrary;
using WholeSlideImageLibrary.Formats.GenericTiff;

namespace WholeSlideImageLibrary.Formats.PhilipsTiff
{
    internal sealed class PhilipsTiffWholeSlideImage : WholeSlideImage
    {
        private readonly PhilipsTiffXmlMetadataScanedImage _metadata;
        private FileSourceReaderCache? _cache;
        private TiffLevel[]? _levels;


        internal PhilipsTiffWholeSlideImage(FileSourceInfo source, PhilipsTiffXmlMetadataScanedImage metadata)
        {
            _cache = new FileSourceReaderCache(source);
            _metadata = metadata;
        }

        internal async Task InitializeAsync(CancellationToken cancellationToken)
        {
            FileSourceReaderCache? cache = _cache;
            Debug.Assert(cache != null);

            TiffFileReader? tiff = await TiffFileReader.OpenAsync(new FileSourceReaderCacheContentSource(cache!), cancellationToken: cancellationToken).ConfigureAwait(false);

            try
            {
                _levels = await GetTiffLevelsAsync(tiff, _metadata.RepresentationSequence!, cancellationToken).ConfigureAwait(false);
            }
            finally
            {
                await tiff.DisposeAsync().ConfigureAwait(false);
            }
        }

        private static async Task<TiffLevel[]> GetTiffLevelsAsync(TiffFileReader tiff, PhilipsTiffXmlMetadataImageLevel[] representationSequence, CancellationToken cancellationToken)
        {
            Debug.Assert(representationSequence.Any(i => i.Number == 0));

            int currentIfd = -1;
            TiffStreamOffset ifdOffset = tiff.FirstImageFileDirectoryOffset;

            int index = 0;
            int baseWidth = 0, baseHeight = 0;
            PhilipsTiffPixelSpacing basePixelSpacing = default;
            TiffLevel[] levels = new TiffLevel[representationSequence.Length];
            foreach (PhilipsTiffXmlMetadataImageLevel? level in representationSequence.OrderBy(i => i.Number))
            {
                TiffImageFileDirectory? ifd = null;

                while (currentIfd < level.Number)
                {
                    if (ifdOffset.IsZero)
                    {
                        throw new InvalidDataException();
                    }
                    ifd = await tiff.ReadImageFileDirectoryAsync(ifdOffset, cancellationToken).ConfigureAwait(false);
                    ifdOffset = ifd.NextOffset;
                    currentIfd++;
                }

                Debug.Assert(ifd != null);
                TiffImageDecoder? decoder = await tiff.CreateImageDecoderAsync(ifd!, cancellationToken).ConfigureAwait(false);
                if (index == 0)
                {
                    baseWidth = decoder.Width;
                    baseHeight = decoder.Height;

                    levels[index++] = new TiffLevel(decoder, level.PixelSpacing, baseWidth, baseHeight);
                    basePixelSpacing = level.PixelSpacing;
                    if (basePixelSpacing.X <= 0 || basePixelSpacing.Y <= 0)
                    {
                        throw new InvalidDataException();
                    }
                }
                else
                {
                    if (level.PixelSpacing.X <= 0 || level.PixelSpacing.Y <= 0)
                    {
                        throw new InvalidDataException();
                    }
                    int width = Math.Min(decoder.Width, (int)Math.Round(basePixelSpacing.X * baseWidth / level.PixelSpacing.X));
                    int height = Math.Min(decoder.Height, (int)Math.Round(basePixelSpacing.Y * baseHeight / level.PixelSpacing.Y));
                    levels[index++] = new TiffLevel(decoder, level.PixelSpacing, width, height);
                }
            }

            return levels;
        }

        private class TiffLevel
        {
            public TiffImageDecoder Decoder { get; }
            public PhilipsTiffPixelSpacing PixelSpacing { get; }
            public int Width { get; }
            public int Height { get; }

            public TiffLevel(TiffImageDecoder decoder, PhilipsTiffPixelSpacing pixelSpacing, int width, int height)
            {
                Decoder = decoder;
                PixelSpacing = pixelSpacing;
                Width = width;
                Height = height;
            }

            public Task ReadRegionAsync<T>(int x, int y, int width, int height, Memory<T> buffer, CancellationToken cancellationToken) where T : unmanaged
            {
                var pixelBuffer = new WholeSlideImagePixelBuffer<T>(buffer, width, height);
                return Decoder.DecodeAsync(new TiffPoint(x, y), pixelBuffer, cancellationToken);
            }
        }

#if false
        internal async Task InitializeAsync(bool identityOnly, CancellationToken cancellationToken)
        {
            FileSourceReaderCache? cache = _cache;
            Debug.Assert(cache != null);

            TiffFileReader? tiff = await TiffFileReader.OpenAsync(new FileSourceReaderCacheContentSource(cache!), cancellationToken: cancellationToken).ConfigureAwait(false);
            List<GenericTiffLevelDecoder>? levels = identityOnly ? null : new List<GenericTiffLevelDecoder>(24);

            TiffFieldReader? fieldReader = null;
            TiffTagReader tagReader;
            uint tileSize = 0;
            try
            {
                fieldReader = await tiff.CreateFieldReaderAsync(cancellationToken).ConfigureAwait(false);

                // 最大的层
                TiffImageFileDirectory ifd = await tiff.ReadImageFileDirectoryAsync(cancellationToken).ConfigureAwait(false);
                TiffImageDecoder decoder = await tiff.CreateImageDecoderAsync(ifd, cancellationToken).ConfigureAwait(false);
                tagReader = new TiffTagReader(fieldReader, ifd);
                TiffPhotometricInterpretation? photometricInterpretation = await tagReader.ReadPhotometricInterpretationAsync(cancellationToken).ConfigureAwait(false);
                TiffCompression compression = await tagReader.ReadCompressionAsync(cancellationToken).ConfigureAwait(false);
                uint? tileWidth = await tagReader.ReadTileWidthAsync(cancellationToken).ConfigureAwait(false);
                uint? tileLength = await tagReader.ReadTileLengthAsync(cancellationToken).ConfigureAwait(false);
                tileSize = tileWidth.HasValue ? tileWidth.GetValueOrDefault() : 256;
                var level0 = new GenericTiffLevelDecoder(decoder, 1);
                level0.SupportFastJpegReading = photometricInterpretation == TiffPhotometricInterpretation.YCbCr && compression == TiffCompression.Jpeg && tileWidth.GetValueOrDefault() == tileSize && tileLength.GetValueOrDefault() == tileSize;
                levels?.Add(level0);
                GenericTiffLevelDecoder? lastLevel = level0;

                if (lastLevel.SupportFastJpegReading)
                {
                    lastLevel.Offsets = (await tagReader.ReadTileOffsetsAsync(cancellationToken).ConfigureAwait(false)).Select(o => (long)o).ToArray();
                    lastLevel.ByteCounts = (await tagReader.ReadTileByteCountsAsync(cancellationToken).ConfigureAwait(false)).Select(c => (int)c).ToArray();
                    lastLevel.JpegTables = await tagReader.ReadJPEGTablesAsync(cancellationToken).ConfigureAwait(false);
                    if (lastLevel.Offsets.Length == 0 || lastLevel.Offsets.Length != lastLevel.ByteCounts.Length)
                    {
                        lastLevel.SupportFastJpegReading = false;
                        lastLevel.Offsets = null;
                        lastLevel.ByteCounts = null;
                        lastLevel.JpegTables = null;
                    }
                }

                // 后续层
                for (TiffStreamOffset ifdOffset = ifd.NextOffset; !ifdOffset.IsZero; ifdOffset = ifd.NextOffset)
                {
                    ifd = await tiff.ReadImageFileDirectoryAsync(ifdOffset, cancellationToken).ConfigureAwait(false);
                    tagReader = new TiffTagReader(fieldReader, ifd);

                    TiffNewSubfileType newSubfileType = await tagReader.ReadNewSubfileTypeAsync(cancellationToken).ConfigureAwait(false);
                    string? imageDescription = await tagReader.ReadImageDescriptionAsync(cancellationToken).ConfigureAwait(false);

                    if ((newSubfileType & TiffNewSubfileType.ReducedResolution) != 0)
                    {
                        decoder = await tiff.CreateImageDecoderAsync(ifd, cancellationToken).ConfigureAwait(false);

                        photometricInterpretation = await tagReader.ReadPhotometricInterpretationAsync(cancellationToken).ConfigureAwait(false);
                        compression = await tagReader.ReadCompressionAsync(cancellationToken).ConfigureAwait(false);
                        tileWidth = await tagReader.ReadTileWidthAsync(cancellationToken).ConfigureAwait(false);
                        tileLength = await tagReader.ReadTileLengthAsync(cancellationToken).ConfigureAwait(false);

                        if (decoder.Width < lastLevel.Width && decoder.Height < lastLevel.Height)
                        {
                            double downsample = Math.Max(level0.Width / (double)decoder.Width, level0.Height / (double)decoder.Height);
                            lastLevel = new GenericTiffLevelDecoder(decoder, downsample);
                            lastLevel.SupportFastJpegReading = photometricInterpretation == TiffPhotometricInterpretation.YCbCr && compression == TiffCompression.Jpeg && tileWidth.GetValueOrDefault() == tileSize && tileLength.GetValueOrDefault() == tileSize;
                            levels?.Add(lastLevel);

                            if (lastLevel.SupportFastJpegReading)
                            {
                                lastLevel.Offsets = (await tagReader.ReadTileOffsetsAsync(cancellationToken).ConfigureAwait(false)).Select(o => (long)o).ToArray();
                                lastLevel.ByteCounts = (await tagReader.ReadTileByteCountsAsync(cancellationToken).ConfigureAwait(false)).Select(c => (int)c).ToArray();
                                lastLevel.JpegTables = await tagReader.ReadJPEGTablesAsync(cancellationToken).ConfigureAwait(false);
                                if (lastLevel.Offsets.Length == 0 || lastLevel.Offsets.Length != lastLevel.ByteCounts.Length)
                                {
                                    lastLevel.SupportFastJpegReading = false;
                                    lastLevel.Offsets = null;
                                    lastLevel.ByteCounts = null;
                                    lastLevel.JpegTables = null;
                                }
                            }
                        }
                    }
                }
            }
            finally
            {
                if (!(fieldReader is null))
                {
                    await fieldReader.DisposeAsync().ConfigureAwait(false);
                }

                await tiff.DisposeAsync().ConfigureAwait(false);
            }

            if (!identityOnly)
            {
                Debug.Assert(levels != null);
                _levels = levels!.ToArray();

                // 只要最大层支持快速JPEG读取，就使用快速DeepZoom生成
                _tileSize = levels[0].SupportFastJpegReading ? (int)tileSize : 0;
            }
        }
#endif


        public override int LevelCount => EnsureNotDisposed().Length;

        public override int Width => EnsureNotDisposed()[0].Width;

        public override int Height => EnsureNotDisposed()[0].Height;

        public override Size GetLevelSize(int level)
        {
            TiffLevel[] levels = EnsureNotDisposed();
            if ((uint)level >= levels.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(level));
            }
            TiffLevel levelDecoder = levels[level];
            return new Size(levelDecoder.Width, levelDecoder.Height);
        }

        public override async ValueTask ReadRegionAsync<T>(int level, int x, int y, int width, int height, Memory<T> buffer, CancellationToken cancellationToken)
        {
            TiffLevel[] levels = EnsureNotDisposed();

            if ((uint)level > (uint)levels.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(level));
            }
            TiffLevel levelDecoder = levels[level];
            if (Unsafe.SizeOf<T>() * buffer.Length < 4 * width * height)
            {
                throw new ArgumentException("destination is too small.", nameof(buffer));
            }

            await levelDecoder.ReadRegionAsync(x, y, width, height, buffer, cancellationToken).ConfigureAwait(false);
        }


        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private TiffLevel[] EnsureNotDisposed()
        {
            TiffLevel[]? levels = _levels;
            if (levels is null)
            {
                ThrowObjectDisposedException();
            }
            return levels;
        }

        [DoesNotReturn]
        private static void ThrowObjectDisposedException()
        {
            throw new ObjectDisposedException(nameof(PhilipsTiffWholeSlideImage));
        }

        public override async ValueTask DisposeAsync()
        {
            _levels = null;

            FileSourceReaderCache? cache = _cache;
            if (!(cache is null))
            {
                _cache = null;
                await cache.DisposeAsync().ConfigureAwait(false);
            }

            await base.DisposeAsync().ConfigureAwait(false);
        }

    }
}
